﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Drawing.Imaging;

namespace GraphicsEditor
{
    public class Intersection
    {
        //will be a matrix nxn where n is number of lines
    }

    public class Line
    {
        public Point pnt1;
        public Point pnt2;

        public Line(Point a, Point b)
        {
            pnt1 = a;
            pnt2 = b;
        }
    }

    public class PolySelectTool : DrawingTool
    {

        private bool isFirstClick = true;
        private int milliseconds = 0;
        private Rectangle doubleClickRectangle = new Rectangle();
        private Rectangle boundingBox = new Rectangle();
        private UnsafeBitmap selectionMask;
        public int pxlSize;
        public Image bitmap;
        private Timer doubleClickTimer = new Timer();
        public bool isSelect, selectChanged, b_drawing, b_moving, b_moving_all;
        public bool draw_mouse_overs = false;

        SetCursorDelegate cursorDelegate;
        InvalidateDelegate _invalidate;

        Point pnt_m_Start;
        Point pnt_m_droploc;
        int MoveIndex;

        List<Point> dropPoints = new List<Point>();

        public PolySelectTool()
        {
            pxlSize = 1;
            isSelect = false;
            selectChanged = false;
            doubleClickTimer.Interval = 100;
            doubleClickTimer.Tick += new EventHandler(doubleClickTimer_Tick);
            b_drawing = false;
            b_moving = false;
            b_moving_all = false;
        }

        public void setCursorDelegate(SetCursorDelegate del)
        {
            cursorDelegate = del;
        }

        public void SetInvalidateDelegate(InvalidateDelegate del)
        {
            _invalidate = del;
        }

        public override bool isSelected()
        {
            return isSelect;
        }

        public override bool handleInput(Keys keys, PictureBoxAdvance pictureWindow)
        {
            int nudge_amount = 5;
            if (Control.ModifierKeys == Keys.Control)
                nudge_amount = 1;

            if (keys == Keys.Enter)
            {
                mergeSelected(pictureWindow);
                return true;
            }
            else if (keys == Keys.Up)
            {
                nudgeUp(nudge_amount, pictureWindow);
            }
            else if (keys == Keys.Down)
            {
                nudgeDown(nudge_amount, pictureWindow);
            }
            else if (keys == Keys.Left)
            {
                nudgeLeft(nudge_amount, pictureWindow);
            }
            else if (keys == Keys.Right)
            {
                nudgeRight(nudge_amount, pictureWindow);
            }
            else if ((Control.ModifierKeys == Keys.Control) && (keys == Keys.D))
            {
                deselect(pictureWindow);
            }
            return false;
        }

        public void nudgeUp(int amount, PictureBoxAdvance picWindow)
        {
            //check to ensure bounding box is not going to move outside the current window
            if (boundingBox.Location.Y - amount <= 0)
            {
                int temp = boundingBox.Location.Y - amount;
                amount += temp;
            }
            if (amount > 0)
            {
                for (int i = 0; i < dropPoints.Count; ++i)
                {
                    Point p = new Point(dropPoints[i].X, dropPoints[i].Y);
                    p.Y -= amount;
                    dropPoints[i] = p;
                }
            }
            SetBoundingBox();
            _invalidate();
        }

        public void nudgeDown(int amount, PictureBoxAdvance picWindow)
        {
            //check to ensure bounding box is not going to move outside the current window
            if (boundingBox.Location.Y + boundingBox.Height + amount >= picWindow.Image.Height)
            {
                int temp = (boundingBox.Location.Y + boundingBox.Height + amount) - picWindow.Image.Height;
                amount -= temp;
            }
            if (amount > 0)
            {
                for (int i = 0; i < dropPoints.Count; ++i)
                {
                    Point p = new Point(dropPoints[i].X, dropPoints[i].Y);
                    p.Y += amount;
                    dropPoints[i] = p;
                }
            }
            SetBoundingBox();
            _invalidate();
        }

        public void nudgeLeft(int amount, PictureBoxAdvance picWindow)
        {
            //check to ensure bounding box is not going to move outside the current window
            if (boundingBox.Location.X - amount <= 0)
            {
                int temp = boundingBox.Location.X - amount;
                amount += temp;
            }
            if (amount > 0)
            {
                for (int i = 0; i < dropPoints.Count; ++i)
                {
                    Point p = new Point(dropPoints[i].X, dropPoints[i].Y);
                    p.X -= amount;
                    dropPoints[i] = p;
                }
            }
            SetBoundingBox();
            _invalidate();
        }

        public void nudgeRight(int amount, PictureBoxAdvance picWindow)
        {
            //check to ensure bounding box is not going to move outside the current window
            if (boundingBox.Location.X + boundingBox.Width + amount >= picWindow.Image.Width)
            {
                int temp = (boundingBox.Location.X + boundingBox.Width + amount) - picWindow.Image.Width;
                amount -= temp;
            }
            if (amount > 0)
            {
                for (int i = 0; i < dropPoints.Count; ++i)
                {
                    Point p = new Point(dropPoints[i].X, dropPoints[i].Y);
                    p.X += amount;
                    dropPoints[i] = p;
                }
            }
            SetBoundingBox();
            _invalidate();
        }


        public override void deselect(PictureBoxAdvance pictureWindow)
        {
            isSelect = false;
            if (bitmap != null)
                mergeSelected(pictureWindow);
            b_drawing = false;
            b_moving = false;
            b_moving_all = false;
            dropPoints = new List<Point>();
            boundingBox = new Rectangle();
            cursorDelegate(Cursors.Default);

            bitmap = null;
            //should merge selected bitmap and picturewindow bitmap

            _invalidate();
        }

        int DRAW_SIZE = 4, DRAW_SUB = 2;
        int MOUSE_SIZE = 26, MOUSE_SUB = 13;

        private Rectangle getMouseRectFromPoint(Point p)
        {
            int X = p.X - MOUSE_SUB;
            int Y = p.Y - MOUSE_SUB;
            return new Rectangle(X, Y, MOUSE_SIZE, MOUSE_SIZE);
        }
        private Rectangle getDrawRectFromPoint(Point p)
        {
            int X = p.X - DRAW_SUB;
            int Y = p.Y - DRAW_SUB;
            return new Rectangle(X, Y, DRAW_SIZE, DRAW_SIZE);
        }

        void doubleClickTimer_Tick(object sender, EventArgs e)
        {
            milliseconds += 100;
            if (milliseconds >= SystemInformation.DoubleClickTime)
            {
                doubleClickTimer.Stop();
                isFirstClick = true;
                milliseconds = 0;

            }
        }

        public void SetBoundingBox()
        {
            int smallestX = int.MaxValue, smallestY = int.MaxValue, largestX = 0, largestY = 0;
            foreach (Point p in dropPoints)
            {
                if (p.X > largestX)
                {
                    largestX = p.X;
                }
                if (p.X < smallestX)
                {
                    smallestX = p.X;
                }
                if (p.Y > largestY)
                {
                    largestY = p.Y;
                }
                if (p.Y < smallestY)
                {
                    smallestY = p.Y;
                }
            }
            //should now have a bounding box
            boundingBox = getRectFromPoints(new Point(smallestX, largestY), new Point(largestX, smallestY));
        }

        public override void setMouseDown(Point mouseLoc, PictureBoxAdvance pictureWindow)
        {
            if (isFirstClick)
            {
                isFirstClick = false;
                doubleClickRectangle = new Rectangle(mouseLoc.X - (SystemInformation.DoubleClickSize.Width / 2),
                    mouseLoc.Y - (SystemInformation.DoubleClickSize.Height / 2),
                    SystemInformation.DoubleClickSize.Width,
                    SystemInformation.DoubleClickSize.Height);
                doubleClickTimer.Start();
                if (bitmap == null)
                {
                    int i = 0;
                    foreach (Point p in dropPoints)
                    {
                        if (getMouseRectFromPoint(p).Contains(mouseLoc) && !b_drawing)
                        {
                            MoveIndex = i;
                            pnt_m_Start = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
                            b_moving = true;
                            return;
                        }
                        ++i;
                    }
                }

                if (isSelect && isInsideSelection(mouseLoc))
                {
                    b_moving_all = true;
                    pnt_m_Start = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
                    return;
                }
                if (b_drawing)
                {
                    pnt_m_Start = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
                }
                else
                {
                    //no points laid or selection is already complete, clicking now will result in new selection
                    if (isSelect)
                    {
                        //currently selected, deselect and dont set drawing flag
                        deselect(pictureWindow);
                    }
                    else
                    {
                        pnt_m_Start = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
                        b_drawing = true;
                    }
                }

            }
            else
            {
                if (doubleClickRectangle.Contains(mouseLoc) && milliseconds < SystemInformation.DoubleClickTime)
                {
                    Point temp = new Point(dropPoints[0].X, dropPoints[0].Y);
                    dropPoints.Add(temp);
                    b_drawing = false;
                    isSelect = true;
                    _invalidate();
                }
            }

        }
        public override void mouseMoved(Point mouseLoc, PictureBoxAdvance pictureWindow, ImageForm imageform, bool mouseDown)
        {
            mouseMoved(mouseLoc, pictureWindow, mouseDown);
        }

        public override void mouseMoved(Point mouseLoc, PictureBoxAdvance pictureWindow, bool mouseDown)
        {
            pnt_m_droploc = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
            if (b_moving)
            {
                //moving a point
                if (MoveIndex == 0 || MoveIndex == dropPoints.Count - 1)
                {
                    //moving either the first of the last so move both
                    dropPoints[0] = pnt_m_droploc;
                    dropPoints[dropPoints.Count - 1] = pnt_m_droploc;
                }
                else
                {
                    dropPoints[MoveIndex] = pnt_m_droploc;
                }
                _invalidate();
                return;
            }
            if (b_moving_all)
            {
                //check to ensure distance moved will not put boundingbox outside picturewindow
                int xDelta = pnt_m_droploc.X - pnt_m_Start.X;
                int yDelta = pnt_m_droploc.Y - pnt_m_Start.Y;
                if (xDelta + boundingBox.Location.X < 0)
                {
                    //cannot move this far to the left
                    xDelta = 0;
                }
                if (xDelta + boundingBox.Location.X + boundingBox.Width > pictureWindow.Width)
                {
                    //cannot move this far to the right
                    xDelta = 0;
                }
                if (yDelta + boundingBox.Location.Y < 0)
                {
                    //cannot move this far up
                    yDelta = 0;
                }
                if (yDelta + boundingBox.Location.Y + boundingBox.Height > pictureWindow.Height)
                {
                    //cannot move this far down
                    yDelta = 0;
                }
                for (int i = 0; i < dropPoints.Count; ++i)
                {
                    Point p = new Point(dropPoints[i].X + xDelta, dropPoints[i].Y + yDelta);
                    dropPoints[i] = p;
                }
                SetBoundingBox();
                pnt_m_Start = pnt_m_droploc;
                _invalidate();
                return;
            }
            bool OverPoint = false;
            if (bitmap == null)
            {
                foreach (Point p in dropPoints)
                {
                    if (getMouseRectFromPoint(p).Contains(mouseLoc))
                    {
                        OverPoint = true;
                    }
                }
            }
            if (OverPoint)
            {
                cursorDelegate(Cursors.Hand);
            }
            else if (isSelect && isInsideSelection(mouseLoc))
            {
                cursorDelegate(Cursors.SizeAll);
            }
            else
            {
                cursorDelegate(Cursors.Default);
            }

        }

        /// <summary>
        /// Three possible states for this function
        /// b_drawing: Initial state after deselect and then mouse down, add point to list
        /// b_moving: Moving an individual point, cannot move points outside pictureWindow
        /// b_moving_all: Moving the whole selection, cannot move any point outside pictureWindow
        /// </summary>
        /// <param name="mouseLoc">Location of the mouse relative to pictureWindow</param>
        /// <param name="pictureWindow">Window containing current image</param>
        public override void setMouseUp(Point mouseLoc, PictureBoxAdvance pictureWindow)
        {
            if (b_drawing)
            {
                pnt_m_droploc = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
                if (dropPoints.Count > 0)
                {
                    Point temp = dropPoints[0];
                    if (getMouseRectFromPoint(temp).Contains(mouseLoc))
                    {
                        //close the selection
                        b_drawing = false;
                        isSelect = true;
                        pnt_m_droploc = temp;
                    }
                }
                dropPoints.Add(pnt_m_droploc);
                SetBoundingBox();

                _invalidate();
            }
            if (b_moving)
            {
                pnt_m_droploc = getPixelPositionUsingMouse(mouseLoc, pictureWindow);
                if (MoveIndex == 0 || MoveIndex == dropPoints.Count - 1)
                {
                    //moving either the first of the last so move both
                    dropPoints[0] = pnt_m_droploc;
                    dropPoints[dropPoints.Count - 1] = pnt_m_droploc;
                }
                else
                {
                    dropPoints[MoveIndex] = pnt_m_droploc;
                }
                SetBoundingBox();
                _invalidate();
                b_moving = false;
            }
            if (b_moving_all)
            {
                int xDelta = pnt_m_droploc.X - pnt_m_Start.X;
                int yDelta = pnt_m_droploc.Y - pnt_m_Start.Y;
                if (xDelta + boundingBox.Location.X < 0)
                {
                    //cannot move this far to the left
                    xDelta = 0;
                }
                if (xDelta + boundingBox.Location.X + boundingBox.Width > pictureWindow.Width)
                {
                    //cannot move this far to the right
                    xDelta = 0;
                }
                if (yDelta + boundingBox.Location.Y < 0)
                {
                    //cannot move this far up
                    yDelta = 0;
                }
                if (yDelta + boundingBox.Location.Y + boundingBox.Height > pictureWindow.Height)
                {
                    //cannot move this far down
                    yDelta = 0;
                }
                for (int i = 0; i < dropPoints.Count; ++i)
                {
                    Point p = new Point(dropPoints[i].X + xDelta, dropPoints[i].Y + yDelta);
                    dropPoints[i] = p;
                }
                SetBoundingBox();
                pnt_m_Start = pnt_m_droploc;
                _invalidate();
                b_moving_all = false;
            }
        }




        SolidBrush hbr = new SolidBrush(Color.FromArgb(25, Color.Black));
        Pen borderPen = new Pen(new SolidBrush(Color.Black));

        SolidBrush whiteBrush = new SolidBrush(Color.White);
        Pen blackPen = new Pen(new SolidBrush(Color.Black));
        Pen greenPen = new Pen(new SolidBrush(Color.Green));

        public void getIntersections()
        {
            List<Intersection> intersectionList = new List<Intersection>();
            List<Line> lineList = new List<Line>();
            for (int i = 1; i < dropPoints.Count; ++i)
            {
                Point pntA = dropPoints[i - 1];
                Point pntB = dropPoints[i];
                lineList.Add(new Line(pntA, pntB));
            }
            //all lines are in the line list, check each line with all other lines

        }
        public bool isInsideSelection(Point pnt)
        {
            selectionMask.LockBitmap();
            PixelData pxl = selectionMask.GetPixel(pnt.X, pnt.Y);
            selectionMask.UnlockBitmap();
            if ((pxl.alpha == (byte)255) && (pxl.green == (byte)0) && (pxl.blue == (byte)0) && (pxl.red == (byte)0))
            {
                return true;
            }

            return false;
        }
        public override void paintSelection(Graphics gfx, PictureBoxAdvance pictureWindow, bool zoom)
        {

            //if (b_drawing)
            // {
            if (!isSelect)
            {
                bool start = true;
                Point prevPoint = new Point();
                List<Rectangle> rectDraw = new List<Rectangle>();
                List<Rectangle> rectMouse = new List<Rectangle>();


                //cant use draw polygon since it is not complete
                foreach (Point p in dropPoints)
                {
                    if (!start)
                    {
                        //after the first point draw a line from prevPoint to p;
                        float[] borderDashPattern = { 5, 3 };
                        borderPen.DashPattern = borderDashPattern;
                        gfx.DrawLine(borderPen, prevPoint, p);
                    }
                    else
                    {
                        start = false;
                    }
                    rectDraw.Add(getDrawRectFromPoint(p));
                    if (draw_mouse_overs)
                        rectMouse.Add(getMouseRectFromPoint(p));
                    prevPoint = p;
                }
                int i = 0;
                foreach (Rectangle r in rectDraw)
                {
                    gfx.FillRectangle(whiteBrush, r);
                    gfx.DrawRectangle(blackPen, r);
                }
                foreach (Rectangle r in rectMouse)
                {
                    gfx.DrawRectangle(blackPen, r);
                    gfx.DrawString(dropPoints[i].ToString(), new Font("", 8), Brushes.Black, new PointF(r.Location.X - 20, r.Location.Y - 20));
                    ++i;
                }
                if (draw_mouse_overs)
                {
                    gfx.DrawRectangle(blackPen, boundingBox);
                }
            }
            if (isSelect)
            {
                if (bitmap != null)
                {
                    gfx.DrawImage(bitmap, boundingBox);
                }
                Brush b = new SolidBrush(Color.FromArgb(20, 255, 255, 255));
                gfx.FillPolygon(b, dropPoints.ToArray());

                gfx.DrawPolygon(borderPen, dropPoints.ToArray());
                //populate the selection mask
                selectionMask = new UnsafeBitmap(pictureWindow.Width, pictureWindow.Height);
                Graphics g = Graphics.FromImage(selectionMask.Bitmap);
                g.FillPolygon(Brushes.Black, dropPoints.ToArray());

                g.Dispose();

                if (bitmap == null)
                {
                    //since there is no bitmap go ahead and draw the squares for mouse interaction
                    for (int i = 0; i < dropPoints.Count - 1; ++i)
                    {
                        gfx.FillRectangle(whiteBrush, getDrawRectFromPoint(dropPoints[i]));
                        gfx.DrawRectangle(blackPen, getDrawRectFromPoint(dropPoints[i]));
                        if (draw_mouse_overs)
                        {
                            Rectangle r = getMouseRectFromPoint(dropPoints[i]);
                            gfx.DrawRectangle(blackPen, r);
                            gfx.DrawString(dropPoints[i].ToString(), new Font("", 8), Brushes.Black, new PointF(r.Location.X - 20, r.Location.Y - 20));

                            gfx.DrawRectangle(blackPen, boundingBox);
                        }
                    }
                }
            }


        }


        public void setPixelSize(PictureBoxAdvance pictureWindow)
        {
            pxlSize = pictureWindow.Width / pictureWindow.Image.Width;
        }

        public override void cut(PictureBoxAdvance pictureWindow)
        {
            if (isSelect)
            {
                UnsafeBitmap workingCopy = new UnsafeBitmap(new Bitmap(pictureWindow.Image));
                UnsafeBitmap copiedImage = new UnsafeBitmap(boundingBox.Width, boundingBox.Height);
                //lockup the bitmaps in memory
                workingCopy.LockBitmap();
                copiedImage.LockBitmap();
                selectionMask.LockBitmap();

                PixelData clear;
                clear.blue = (byte)255;
                clear.green = (byte)255;
                clear.red = (byte)255;
                clear.alpha = (byte)0;
                //copy pixel by pixel, use selection mask to determine appropriate action
                int copyX = 0, copyY = 0;
                for (int y = boundingBox.Location.Y; y < boundingBox.Location.Y + boundingBox.Height; ++y)
                {
                    for (int x = boundingBox.Location.X; x < boundingBox.Location.X + boundingBox.Width; ++x)
                    {
                        PixelData pxl = selectionMask.GetPixel(x, y);
                        if ((pxl.alpha == (byte)255) && (pxl.green == (byte)0) && (pxl.blue == (byte)0) && (pxl.red == (byte)0))
                        {
                            //pixel has been selected
                            copiedImage.SetPixel(copyX, copyY, workingCopy.GetPixel(x, y));
                            workingCopy.SetPixel(x, y, clear);
                        }
                        else
                        {
                            copiedImage.SetPixel(copyX, copyY, clear);
                        }
                        ++copyX;
                    }
                    copyX = 0;
                    ++copyY;
                }
                //unlock the bitmaps
                workingCopy.UnlockBitmap();
                selectionMask.UnlockBitmap();
                copiedImage.UnlockBitmap();
                using (var stream = new MemoryStream())
                {
                    copiedImage.Bitmap.Save(stream, ImageFormat.Png);
                    var data = new DataObject();
                    data.SetData(DataFormats.Bitmap, true, copiedImage.Bitmap);
                    data.SetData("PNG", true, stream);
                    data.SetData("DROPPOINTS", false, dropPoints);

                    Clipboard.Clear();
                    Clipboard.SetDataObject(data, true);
                }
                pictureWindow.Image = workingCopy.Bitmap;
            }
        }

        public override void copy(PictureBoxAdvance pictureWindow)
        {
            if (isSelect)
            {
                UnsafeBitmap workingCopy = new UnsafeBitmap(new Bitmap(pictureWindow.Image));
                UnsafeBitmap copiedImage = new UnsafeBitmap(boundingBox.Width, boundingBox.Height);
                //lockup the bitmaps in memory
                workingCopy.LockBitmap();
                copiedImage.LockBitmap();
                selectionMask.LockBitmap();

                PixelData clear;
                clear.blue = (byte)255;
                clear.green = (byte)255;
                clear.red = (byte)255;
                clear.alpha = (byte)0;
                //copy pixel by pixel, use selection mask to determine appropriate action
                int copyX = 0, copyY = 0;
                for (int y = boundingBox.Location.Y; y < boundingBox.Location.Y + boundingBox.Height; ++y)
                {
                    for (int x = boundingBox.Location.X; x < boundingBox.Location.X + boundingBox.Width; ++x)
                    {
                        PixelData pxl = selectionMask.GetPixel(x, y);
                        if ((pxl.alpha == (byte)255) && (pxl.green == (byte)0) && (pxl.blue == (byte)0) && (pxl.red == (byte)0))
                        {
                            //pixel has been selected
                            copiedImage.SetPixel(copyX, copyY, workingCopy.GetPixel(x, y));
                        }
                        else
                        {
                            copiedImage.SetPixel(copyX, copyY, clear);
                        }
                        ++copyX;
                    }
                    copyX = 0;
                    ++copyY;
                }
                //unlock the bitmaps
                workingCopy.UnlockBitmap();
                selectionMask.UnlockBitmap();
                copiedImage.UnlockBitmap();
                using (var stream = new MemoryStream())
                {
                    copiedImage.Bitmap.Save(stream, ImageFormat.Png);
                    var data = new DataObject();
                    data.SetData(DataFormats.Bitmap, true, copiedImage.Bitmap);
                    data.SetData("PNG", true, stream);
                    data.SetData("DROPPOINTS", false, dropPoints);

                    Clipboard.Clear();
                    Clipboard.SetDataObject(data, true);
                }
                deselect(pictureWindow);
            }
        }

        public override void deleteSelected(PictureBoxAdvance pictureWindow)
        {
            //save the start and drop points

        }

        public override void paste()
        {
            var temp = Clipboard.GetData("PNG");
            if (temp != null)
            {
                Console.WriteLine("PasteWorked");
                bitmap = Image.FromStream((MemoryStream)temp);
            }
            temp = Clipboard.GetData("DROPPOINTS");
            if (temp != null)
            {
                dropPoints = (List<Point>)temp;
                SetBoundingBox();
                isSelect = true;
            }
        }

        public void mergeSelected(PictureBoxAdvance pictureWindow)
        {
            if (bitmap != null)
            {
                Graphics gfx = Graphics.FromImage(pictureWindow.Image);
                gfx.DrawImage(bitmap, boundingBox);
                bitmap = null;
                deselect(pictureWindow);
            }
        }
    }
}